#pragma once

#include "fxcc/core/graph/opengl3/pch.h"

namespace fxcc
{
    namespace graph
    {
        namespace opengl3
        {

            struct FXCC_API Program 
            {

                Program(){
                    Create();
                }
                
                static void UnUse();

                unsigned int m_Id = 0;

                bool Init2();

                void Use() const;

                void use() const
                {
                    Use();
                }
                void Create();
                
                void Release();
                
                void Link();

                void Delete() const;

                void Bind(const std::string& name, unsigned int binding);

                unsigned int GetUniformBlockIndex(const std::string& name);

                unsigned int GetLocation(const std::string& name) const
                {
                    unsigned int res = glGetUniformLocation(m_Id, name.c_str());

                    return res;
                }

                void setBool(const std::string& name, bool value) const
                {
                    glUniform1i(GetLocation(name), (int)value);
                }
                // ------------------------------------------------------------------------
                void setInt(const std::string& name, int value) const
                {
                    glUniform1i(GetLocation(name), value);
                }
                // ------------------------------------------------------------------------
                void setFloat(const std::string& name, float value) const
                {
                    glUniform1f(GetLocation(name), value);
                }
                void SetFloatArray(const std::string& name, float* val, int num)
                {
                    for (int i = 0; i < num; i++)
                    {
                        std::string label = name + std::string("[") + std::to_string(i) + "]";
                        setFloat(label, val[i]);
                    }
                }

                void setVec2(const std::string& name, const float* value) const
                {
                    glUniform2fv(GetLocation(name), 1, &value[0]);
                }
                void setVec2(const std::string& name, float x, float y) const
                {
                    glUniform2f(GetLocation(name), x, y);
                }
                // ------------------------------------------------------------------------
                void setVec3(const std::string& name, const float* value) const
                {
                    glUniform3fv(GetLocation(name), 1, &value[0]);
                }
                void setVec3(const std::string& name, float x, float y, float z) const
                {
                    glUniform3f(GetLocation(name), x, y, z);
                }
                // ------------------------------------------------------------------------
                void setVec4(const std::string& name, const float* value, const int num = 1) const
                {
                    glUniform4fv(GetLocation(name), num, &value[0]);
                }
                void setVec4(const std::string& name, float x, float y, float z, float w)
                {
                    glUniform4f(GetLocation(name), x, y, z, w);
                }
                // ------------------------------------------------------------------------
                void setIVec2(const std::string& name, const int* value) const
                {
                    glUniform2iv(GetLocation(name), 1, value);
                }
                void setIVec2(const std::string& name, const glm::ivec2& v) const
                {
                    glUniform2iv(GetLocation(name), 1, &v.x);
                }
                // ------------------------------------------------------------------------
                void setIVec3(const std::string& name, const int* value) const
                {
                    glUniform3iv(GetLocation(name), 1, value);
                }
                void setIVec3(const std::string& name, const glm::ivec3& v) const
                {
                    glUniform3iv(GetLocation(name), 1, &v.x);
                }
                // ------------------------------------------------------------------------
                void setIVec4(const std::string& name, const int* value) const
                {
                    glUniform4iv(GetLocation(name), 1, value);
                }
                void setIVec4(const std::string& name, const glm::ivec4& v) const
                {
                    glUniform4iv(GetLocation(name), 1, &v.x);
                }
                // ------------------------------------------------------------------------
                void setMat2(const std::string& name, const float* mat) const
                {
                    glUniformMatrix2fv(GetLocation(name), 1, GL_FALSE, &mat[0]);
                }
                // ------------------------------------------------------------------------
                void setMat3(const std::string& name, const float* mat) const
                {
                    glUniformMatrix3fv(GetLocation(name), 1, GL_FALSE, &mat[0]);
                }
                // ------------------------------------------------------------------------
                void setMat4(const std::string& name, const float* mat) const
                {
                    glUniformMatrix4fv(GetLocation(name), 1, GL_FALSE, &mat[0]);
                }

                void setVec2(const std::string& name, const glm::vec2& v) const
                {
                    this->setVec2(name, &v.x);
                }

                void setVec3(const std::string& name, const glm::vec3& v) const
                {
                    this->setVec3(name, &v.x);
                }

                void setVec4(const std::string& name, const glm::vec4& v) const
                {
                    this->setVec4(name, &v.x);
                }


                void setMat2(const std::string& name, const glm::mat2 mat) const
                {
                    this->setMat2(name, &mat[0][0]);
                }
                // ------------------------------------------------------------------------
                void setMat3(const std::string& name, const glm::mat3 mat) const
                {
                    this->setMat3(name, &mat[0][0]);
                }
                void setMat4(const std::string& name, const glm::mat4& mat) const
                {
                    this->setMat4(name, &mat[0][0]);
                }
                virtual ~Program();
            };


        };
    }
};